const cvs = document.getElementById("mainCanvas");
const ctx = cvs.getContext('2d');
const _createMenu = new CreateMenu();
var _editMenu = null;
const camera = new Camera();
var nodes = [];
var lines = [];
// 由于生成对话的功能是放在按钮点击事件中的，而点击事件传的参数不能修改，所以用一个全局变量来决定插入哪里
var _addContentIndex = 0;
// 正在编辑的对话
var _editContent = null;
var _selectedNode = null;

// 每次更改窗口大小都会将cvs的大小也更改了
window.onresize = function(e) {
    cvs.width = window.innerWidth;
    cvs.height = window.innerHeight;
}

// 将原本的鼠标右键事件进行禁用
window.oncontextmenu = function(e) {
    e.preventDefault();
}

function init() {
    cvs.width = window.innerWidth;
    cvs.height = window.innerHeight;
    ctx.font = "16px Arial";

    cvs.onmousedown = defaultCanvasClickEvent;
    window.onkeydown = defaultKeydownEvent;
    const contentSubmitBtn = document.getElementById("contentSubmitBtn");
    contentSubmitBtn.onclick = btnAddContentEvent;
}

/** 默认鼠标点击事件 */
function defaultCanvasClickEvent(e) {
    // 无论如何都关闭输入菜单
    let inputfield = document.getElementById("inputfield");
    inputfield.style.display = "none";

    // 无论如何先得到鼠标位置
    let x = camera.getWorldX(e.clientX);
    let y = camera.getWorldY(e.clientY);
    // 由于可能会涉及到拖动事件，所以新建这两个变量
    let lastx = e.clientX;
    let lasty = e.clientY;

    if (e.button == 2) {
        /**
         * 鼠标右键点击
         * (1)若在节点处点击，则打开节点的编辑菜单
         * (2)若在空白处点击则是呼出创建菜单
         */

        // 先判断是否是点击在节点处
        _selectedNode = null;
        let i = nodes.length - 1;   // 后往前遍历，因为迟渲染节点在后面
        for (; i >= 0; i--) {
            if (isInside(nodes[i], x, y)) {
                _selectedNode = nodes[i];
                break;
            }
        }
        if (_selectedNode) { // 如果被选中节点不为空，则是有选中物体，执行(1)
            // 将节点设为选中状态，其余节点设为非选中状态
            _selectedNode.selected = true;
            for (let j = i + 1; j < nodes.length; j++) {
                nodes[j].selected = false;
            }
            for (let j = i - 1; j >= 0; j--) {
                nodes[j].selected = false;
            }

            _editMenu = _selectedNode.genEditMenu(x, y);

            _createMenu.unshow();
            return; // 执行完则函数返回，不再执行后面部分
        }

        // 若没有被返回，则执行(2)
        _selectedNode = null;
        _editMenu = null;
        _createMenu.show(camera.getWorldX(e.clientX), camera.getWorldY(e.clientY));

    } else if (e.button == 0) {
        /**
         * 鼠标左键点击
         * （1）在菜单处点击则是选择选项
         * （2）在节点处点击则是移动节点
         * （3）在可调整大小节点右下角拖动则是调整节点大小
         */
        
        // 由于有多种交互的可能，所以这个变量记录之前有无已经交互过
        let hasInteracted = false;

        /**
         * 先判断是否与菜单交互
         */
        if (_createMenu.active && isInside(_createMenu, x, y)) {  // 如果创建菜单被点击
            hasInteracted = true;
            let chosenIndex = parseInt((y - _createMenu.y) / 30);    // 向下取整
            _createMenu.createNode(chosenIndex);            
        }
        _createMenu.unshow();    // 无论如何都会关闭菜单
        if (hasInteracted) return;

        /**
         * 再判断是否是选中编辑菜单
         */
        if (_editMenu && isInside(_editMenu, x, y)) {  // 如果创建菜单被点击
            hasInteracted = true;
            let chosenIndex = parseInt((y - _editMenu.y) / 30);    // 向下取整
            _editMenu.handleOption(chosenIndex);
        }
        _editMenu = null;    // 无论如何都关闭菜单 
        if (hasInteracted) return;

        /**
         * 最后再判断是否与节点交互
         */
        _selectedNode = null;
        // 后往前遍历，因为后面的节点覆盖在上面
        // 将i提取出来是因为i在之后还有用
        let i = nodes.length - 1;
        for (; i >= 0; i--) {
            if (isInside(nodes[i], x, y)) {
                _selectedNode = nodes[i];
                break;
            }
        }
        if (_selectedNode) { // 如果被选中节点不为空，则是有选中物体，执行（2）
            // 如果之前选中的不是此节点，则将此节点设为选中并将其余节点全部设为不选中
            if (!_selectedNode.selected) {
                _selectedNode.selected = true;
                for (let j = i + 1; j < nodes.length; j++) {
                    nodes[j].selected = false;
                }
                for (let j = i - 1; j >= 0; j--) {
                    nodes[j].selected = false;
                }
            }
            // 这里也要求助于这几个变量（见中键）
            cvs.onmousemove = function(e) {
                let curx = e.clientX
                let cury = e.clientY;
                _selectedNode.x -= (lastx - curx);
                _selectedNode.y -= (lasty - cury);
                lastx = curx;
                lasty = cury;
            }           
            // 每次移动完将鼠标的移动事件取消掉
            cvs.onmouseup = function(e) {
                cvs.onmousemove = null;
            }  
        } else {    // 没有选中则将所有的节点都设为不选中
            for (let j = 0; j < nodes.length; j++) {
                nodes[j].selected = false;
            }
        }   

    } else if (e.button == 1) {
        /**
         * 鼠标中键点击
         * 移动视角
         */
        cvs.onmousemove = function(e) {
            // 记录下现在的x和y
            let curx = e.clientX;
            let cury = e.clientY;
            // 移动
            camera.move(lastx - curx, lasty - cury);
            // 移动完后将上一次坐标替换成这次
            lastx = curx;
            lasty = cury;
        }
        // 每次移动完将鼠标的移动事件取消掉
        cvs.onmouseup = function(e) {
            cvs.onmousemove = null;
        }
    }
}

/** 键盘输入事件 */
// 由于键盘输入事件有两套（不在输入框输入时和在输入框输入时），所以将事件函数抽离
function defaultKeydownEvent(e) {
    switch (e.key) {
        case 'Delete': {
            for (let i = 0; i < nodes.length; i++) {
                if (nodes[i].selected) {
                    deleteNode(i);
                    break;  // 退出循环
                }
            }
            break;
        }
    }
}


/**
 * 
 * @param {Clickable} item Clickable接口即为有x, y, width, height属性的物体
 * @param {int} x 
 * @param {int} y 
 * @returns 鼠标点击位置是否在它里面
 */
function isInside(item, x, y) {
    return (x >= item.x && x <= item.x + item.width && y >= item.y && y <= item.y + item.height)
}

/**
 * 删除节点数组中指定下标的节点
 * @param {int} index  要删除的下标
 */
function deleteNode(index) {
    if (index >= nodes.length || index < 0) return;

    // 以创建新数组的方式来进行删除，一般来说节点数不大，性能应该足够
    let newNodes = new Array(nodes.length - 1); //提前分配好空间，push的话可能会导致重新分配，这样的话可以节约性能
    let j = 0;  // 这个是插在新数组的下标
    for (let i = 0; i < nodes.length; i++) {
        if (i != index) {
            newNodes[j++] = nodes[i];
        }
    }
    nodes = newNodes;
}

/**
 * 插入对话时“确定”按钮的事件
 */
function btnAddContentEvent() {
    let speaker = document.getElementById("speakerInput").value;
    let text = document.getElementById("contentInput").value;
    if (text != null) {
        let content = new Content(text, speaker);
        _selectedNode.addContent(content, _addContentIndex);
    }
    // 无论如何都将下标归零
    _addContentIndex = 0;
    const inputfield = document.getElementById("inputfield");
    inputfield.style.display = "none"; 
}

/**
 * 编辑对话时“确定”按钮的事件
 */
function btnEditContentEvent() {
    let speaker = document.getElementById("speakerInput").value;
    let text = document.getElementById("contentInput").value;
    if (text != null) {
        _editContent.speaker = speaker;
        _editContent.setText(text);
        _selectedNode.updateHeight();
    }
    const inputfield = document.getElementById("inputfield");
    inputfield.style.display = "none"; 
    // 每次搞完都将按钮事件变回添加
    contentSubmitBtn.onclick = btnAddContentEvent;
}

/**
 * 主函数，程序的主入口
 */
function main() {
    init();
    camera.render();
}

// 执行主函数
main();